iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
Software Development

打造 OS Kernel:從 OS in 1,000 Lines 到 xv6系列 第 19 篇

Day 19|進入 xv6:把自己寫的 Tiny OS 對映到 Unix-like Kernel

  • 分享至 

  • xImage
  •  

QEMU 印出 init: starting sh,接著出現 $。
這次不必重新編譯核心才能換一個測試,我們可以在 Shell 輸入 echo day19-ready,再執行另一個程式。

前面的 Tiny Kernel 已經讓我們碰過組成這個畫面的零件,現在要看一個較完整的系統如何把它們接起來。
今天不急著修改 scheduler,而是先取得可重現的 xv6,再替熟悉的功能找到原始碼位置。

xv6 比較完整,但不是縮小版的現代 Linux

xv6 是 MIT 用於教學的 Unix-like 作業系統,這裡採用 RISC-V 版本。
它提供行程、系統呼叫與檔案系統等功能,程式規模仍適合沿著單一呼叫路徑閱讀。
它的教學定位不代表已具備生產系統的完整安全性或裝置支援。

《Operating System Concepts》第 10 版第 2.8.1 節介紹 monolithic structure,也就是多項核心服務位於同一個核心位址空間。
xv6 的不同 .c 檔案是程式碼分工,不代表每個服務都在獨立的保護區域。
今天建立的 source map 要回答「誰負責這件事」,不能直接把檔案邊界解讀成權限邊界。

先換對平台,再談移植

本系列從這一天切換為 RV64。
兩個專案保留在不同目錄,不把 Tiny Kernel 的 Makefile 或韌體直接覆蓋到 xv6。

項目 Day 11~18 範例 本文 xv6-riscv
CPU 目標 RV32 RV64
一般暫存器寬度 32 bits 64 bits
分頁 Sv32,兩層 Sv39,三層
PTE 大小 4 bytes 8 bytes
每層索引 10 bits 9 bits
核心入口 OpenSBI 交給 S-mode payload -bios none,核心入口從 M-mode 開始
磁碟驅動 本系列 legacy VirtIO MMIO xv6 modern VirtIO MMIO

ra、sp 與 s0 等暫存器名稱看起來相同,但資料寬度已不同。
Day 11 的 sw/lw 與 context offset 不能直接貼進 xv6 的 swtch.S。
同樣地,Sv32 的 VPN 拆法也不能拿來解讀 Sv39 頁表。

Checkpoint 1:固定原始碼版本

以下在 Kali 或 Debian 系開發環境操作,套件安裝需要管理員權限:

sudo apt update
sudo apt install -y build-essential git gcc-riscv64-linux-gnu \
    binutils-riscv64-linux-gnu qemu-system-misc gdb-multiarch

確認工具能被找到:

riscv64-linux-gnu-gcc --version
qemu-system-riscv64 --version
gdb-multiarch --version

本文固定的 xv6 Makefile 要求 QEMU 至少 7.2,本文編寫時的啟動測試使用 8.2.2。
請以實際版本輸出確認,不只看套件安裝指令是否成功。

從 30-days-os-kernel/ 目錄建立一份新的 checkout:

git clone https://github.com/mit-pdos/xv6-riscv.git examples/xv6-riscv
cd examples/xv6-riscv
git checkout --detach 9e3161a9abf5f51ea402562d1874caf6c4926597
git rev-parse HEAD

若目的目錄已存在,先確認是不是你之前修改過的 xv6,不要刪除它來重跑教學。
可以改用另一個空目錄,並相應調整後面的路徑。

固定 commit 能避免文章中的函式名稱與你下載的版本不同。
這裡的 detach 只是閱讀基準,之後要修改 xv6 時可以從這個位置建立自己的分支。

Checkpoint 2:取得真正的 Shell

目前位於 30-days-os-kernel/examples/xv6-riscv/:

make -j4 TOOLPREFIX=riscv64-linux-gnu- kernel/kernel fs.img
make TOOLPREFIX=riscv64-linux-gnu- CPUS=1 qemu

先使用單一 hart,讓第一輪觀察不受多核心輸出交錯影響。
預期終端機會出現:

xv6 kernel is booting

init: starting sh
$

接著在 xv6 的 Shell 輸入,不是在 Kali 的 Shell 輸入:

echo day19-ready
ls

echo 應印出測試字串,ls 則列出磁碟映像中的檔案。
這比只看見開機標語多驗證了一段使用者程式、系統呼叫與檔案系統的整合路徑,但仍不是完整回歸測試。
按 Ctrl+A 再按 X 離開 QEMU,磁碟映像仍保留在 checkout 中。

用已經寫過的功能定位原始碼

不要從第一個檔案一路讀到最後一個檔案。
先問一個具體問題,再找到回答它的資料結構與函式。

想找的責任 xv6 入口 前面做過的事
建立核心入口與堆疊 kernel/entry.S、start.c、main.c Boot 與 kernel_main()
配置實體頁面 kernel/kalloc.c alloc_pages()/free_pages()
保存行程資料 kernel/proc.h、proc.c struct process 與 state
轉移執行上下文 kernel/swtch.S switch_context()
建立與查詢頁表 kernel/vm.c vm_map()/vm_walk()
接收使用者 Trap kernel/trampoline.S、trap.c Trap Frame 與堆疊切換
分派系統呼叫 kernel/syscall.c 依 a7 選擇 handler
讀取裝置與檔案 kernel/virtio_disk.c、bio.c、fs.c disk_read() 與 fs_read()

這是責任對照,不是函式一對一移植表。
例如 xv6 的 user trap 路徑要處理使用者與核心頁表切換,複雜度已超過 Day 17 共用核心映射的做法。

在 xv6 checkout 執行:

rg -n 'struct proc|scheduler\(|allocproc\(' kernel/proc.h kernel/proc.c
rg -n 'walk\(|mappages\(' kernel/vm.c
rg -n 'usertrap\(|syscall\(' kernel/trap.c kernel/syscall.c

若環境沒有 rg,可先安裝 ripgrep,或使用編輯器搜尋相同符號。
把搜尋結果和 source-map.md 對照,挑一條熟悉的責任寫下「輸入狀態、修改狀態、下一個函式」,不要只抄行號。

今天留下的是可重現的起點

本日成果包含固定的 xv6 commit、成功啟動的 Shell,以及 版本與操作筆記 和 source-map.md。
上游 checkout 有自己的 Git repository,外層文章 repository 不要把它當成一般目錄直接加入,應使用獨立 repository 或明確設定的 submodule 管理。

建議文章與筆記的 commit:

day19: pin xv6 baseline and map kernel responsibilities

明天從 _entry 停住 CPU,沿著 start() 走到 main(),確認這個較完整的核心到底如何開始執行。

參考資料


上一篇
Day 18|從 VirtIO 到檔案:把 Tiny Kernel 變成一個小型 OS
下一篇
Day 20|xv6 Boot:從 entry.S 一路 Trace 到 main()
系列文
打造 OS Kernel:從 OS in 1,000 Lines 到 xv6 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言